package com.glsx.auto;

import com.github.javaparser.JavaParser;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.body.Parameter;
import com.github.javaparser.ast.type.ClassOrInterfaceType;
import com.github.javaparser.ast.type.Type;
import com.github.javaparser.javadoc.JavadocBlockTag;
import com.google.common.base.Splitter;
import org.apache.commons.lang3.StringUtils;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;

import javax.xml.bind.annotation.adapters.HexBinaryAdapter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.glsx.auto.Constant.*;
import static org.apache.poi.xwpf.usermodel.ParagraphAlignment.LEFT;



@Mojo(name = "auto-doc",
        aggregator = true, // --non-recursive
        defaultPhase = LifecyclePhase.PROCESS_SOURCES
)
public class AutoDocMojo extends AbstractAutoMojo {

    public static byte[] hexToBytes(String hexString) {
        HexBinaryAdapter adapter = new HexBinaryAdapter();
        byte[] bytes = adapter.unmarshal(hexString);
        return bytes;
    }

    private static void addCustomHeadingStyle(XWPFDocument docxDocument, String strStyleId, int headingLevel, int pointSize, String hexColor) {

        CTStyle ctStyle = CTStyle.Factory.newInstance();
        ctStyle.setStyleId(strStyleId);

        CTString styleName = CTString.Factory.newInstance();
        styleName.setVal(strStyleId);
        ctStyle.setName(styleName);

        CTDecimalNumber indentNumber = CTDecimalNumber.Factory.newInstance();
        indentNumber.setVal(BigInteger.valueOf(headingLevel));

        // lower number > style is more prominent in the formats bar
        ctStyle.setUiPriority(indentNumber);

        CTOnOff onoffnull = CTOnOff.Factory.newInstance();
        ctStyle.setUnhideWhenUsed(onoffnull);

        // style shows up in the formats bar
        ctStyle.setQFormat(onoffnull);

        // style defines a heading of the given level
        CTPPr ppr = CTPPr.Factory.newInstance();
        ppr.setOutlineLvl(indentNumber);
        ctStyle.setPPr(ppr);

        XWPFStyle style = new XWPFStyle(ctStyle);

        CTHpsMeasure size = CTHpsMeasure.Factory.newInstance();
        size.setVal(new BigInteger(String.valueOf(pointSize)));
        CTHpsMeasure size2 = CTHpsMeasure.Factory.newInstance();
        size2.setVal(new BigInteger("24"));

//        CTFonts fonts = CTFonts.Factory.newInstance();
//        fonts.setAscii("Loma" );

        CTRPr rpr = CTRPr.Factory.newInstance();
//        rpr.setRFonts(fonts);
        rpr.setSz(size);
        rpr.setSzCs(size2);

        CTColor color=CTColor.Factory.newInstance();
        color.setVal(hexToBytes(hexColor));
        rpr.setColor(color);
        style.getCTStyle().setRPr(rpr);
        // is a null op if already defined

        // is a null op if already defined
        XWPFStyles styles = docxDocument.createStyles();

        style.setType(STStyleType.PARAGRAPH);
        styles.addStyle(style);

    }

    private void writeDoc(XWPFDocument doc, String filename) {
        FileOutputStream out = null;
        try {
            out = new FileOutputStream(new File(filename));
            doc.write(out);
            out.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    public static void insertTable(XWPFDocument document, String[][] content, int rows, int cols) {
        XWPFTable table = document.createTable(rows, cols);

        for (int row = 0; row < rows; row++) {
            XWPFTableRow tableRow = table.getRow(row);
            CTTrPr trPr = tableRow.getCtRow().addNewTrPr();
            CTHeight ht = trPr.addNewTrHeight();
            ht.setVal(BigInteger.valueOf(600L));

            for (int col = 0; col < cols; ++col) {
                XWPFTableCell cell = tableRow.getCell(col);

                CTTcPr tcpr = cell.getCTTc().addNewTcPr();
                CTVerticalJc va = tcpr.addNewVAlign();
                va.setVal(STVerticalJc.CENTER);

                if(col != cols-1) {
                    tcpr.addNewTcW().setW(BigInteger.valueOf(3000L));
                } else {
                    tcpr.addNewTcW().setW(BigInteger.valueOf(6000L));
                }

                CTShd ctshd = tcpr.addNewShd();
                ctshd.setColor("auto");
                ctshd.setVal(STShd.CLEAR);

                if (row == 0) {
                    //填充表头
                    ctshd.setFill("9BCDFD");
                } else {
                    ctshd.setFill("F8F8F8");
                }

                // 水平居中
                cell.getParagraphs().get(0).setAlignment(ParagraphAlignment.CENTER);
                cell.setText(content[row][col]);
            }
        }
    }

    private static void insertServiceName(XWPFDocument doc, ClassOrInterfaceDeclaration cd) {

        XWPFParagraph p = doc.createParagraph();
        p.setStyle(HEAD_STYLE_1);
        p.setAlignment(LEFT);

        XWPFRun run = p.createRun();
        run.setBold(true);

        if(cd.getComment().isPresent()) {
            run.setText(cd.getComment().get().getContent() + cd.getNameAsString());
            return;
        } else if(cd.getJavadoc().isPresent()) {
            List<String> lines = Splitter.onPattern("\r?\n").limit(2).trimResults()
                    .omitEmptyStrings().splitToList(cd.getJavadoc().get().getDescription().toText());

            if(!lines.isEmpty()) {
                run.setText(lines.get(0) + cd.getNameAsString());
                return;
            }
        }
        run.setText(cd.getNameAsString());
    }

    private static void insertMethodBrief(XWPFDocument doc, MethodDeclaration md) {
        XWPFParagraph p = doc.createParagraph();
        p.setStyle(HEAD_STYLE_2);
        p.setAlignment(LEFT);

        XWPFRun run = p.createRun();
        run.setBold(true);

        if(md.getJavadoc().isPresent()) {
            String[] lines = md.getJavadoc().get().getDescription().toText().split("\r?\n");
            if(lines.length > 0) {
                run.setText(lines[0].trim()+"("+md.getName().getIdentifier()+")");
                return;
            }
        }
        run.setText(md.getName().getIdentifier());
    }

    private static void insertMethodDetail(XWPFDocument doc, MethodDeclaration md) {
        XWPFParagraph p = doc.createParagraph();
        XWPFRun r = p.createRun();
        r.setBold(true);
        r.setText("接口描述");

        p = doc.createParagraph();
        p.setIndentationFirstLine(400);

        if(md.getJavadoc().isPresent()) {
            List<String> lines = Splitter.onPattern("\r?\n").limit(2).trimResults()
                    .omitEmptyStrings().splitToList(md.getJavadoc().get().getDescription().toText());

            p.createRun().setText(lines.size() == 1 ? lines.get(0) : lines.size() == 2 ? lines.get(1) : "");
        }

        p.createRun().addCarriageReturn();
    }

    private static void insertMethodParamTable(XWPFDocument doc, MethodDeclaration md) {

        if(md.getParameters().size() == 0) {
            return;
        }

        XWPFParagraph p = doc.createParagraph();
        XWPFRun r = p.createRun();
        r.setBold(true);
        r.setText("输入参数");

        int totalRow = md.getParameters().size() + 1;
        int totalCol = 4;

        String[][] content = new String[totalRow][totalCol];
        content[0][0] = "参数名称";
        content[0][1] = "类型";
        content[0][2] = "是否必填";
        content[0][3] = "描述信息";

        Map<String, String> descMap = new HashMap<String,String>(md.getParameters().size());
        if(md.getJavadoc().isPresent()) {
            for(JavadocBlockTag tag : md.getJavadoc().get().getBlockTags()) {
                if(tag.getName().isPresent() && "PARAM".equals(tag.getType().name())) {
                    descMap.put(tag.getName().get(), tag.getContent().toText());
                }
            }
        }

        int i = 1;
        for (Parameter parameter : md.getParameters()) {
            content[i][0] = parameter.getNameAsString();
            content[i][1] = parameter.getType().toString();
            content[i][2] = "Y";
            content[i++][3] = descMap.getOrDefault(parameter.getNameAsString(), "");
        }

        insertTable(doc, content, totalRow, totalCol);
    }

    private static void insertReturnValue(XWPFDocument doc, MethodDeclaration md) {

        XWPFParagraph p = doc.createParagraph();
        p.createRun().addCarriageReturn();
        XWPFRun r = p.createRun();
        r.setBold(true);
        r.setText("返回参数");

        String returnDesc = "";

        if(md.getJavadoc().isPresent()) {
            for(JavadocBlockTag tag : md.getJavadoc().get().getBlockTags()) {
                if("RETURN".equals(tag.getType().name())) {
                    returnDesc = tag.getContent().toText();
                    break;
                }
            }
        }

        p = doc.createParagraph();
        p.setIndentationFirstLine(400);
        p.createRun().setText(md.getType().toString() + " " + returnDesc);
        p.createRun().addCarriageReturn();
    }

    @Override
    public void process() {
        final XWPFDocument document = new XWPFDocument();

        addCustomHeadingStyle(document, HEAD_STYLE_1, 1, 32, "000000");
        addCustomHeadingStyle(document, HEAD_STYLE_2, 2, 26, "000000");

        if(StringUtils.isEmpty(servicedir)) {
            servicedir = common.getPackageDirectory(DefaultPackage.COMMON_SERVICE);
        }

        getLog().info(servicedir);

        File[] files = new File(servicedir).listFiles();

        if(files == null) {
            getLog().info("files null");
            return;
        }

        File iServiceFile = Utils.javaFile(common.getPackageDirectory(DefaultPackage.COMMON_BASE), I_SERVICE);

        DocVoidVisitorAdapter iServiceDoc = null;

        if(iServiceFile.exists()) {
            try {
                iServiceDoc = new DocVoidVisitorAdapter(JavaParser.parse(iServiceFile));
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }


        for (File file : files) {
            if(!file.getName().endsWith(".java")) {
                continue;
            }
            try {
                DocVoidVisitorAdapter visitor = new DocVoidVisitorAdapter(JavaParser.parse(file));

                ClassOrInterfaceDeclaration cd = visitor.getClassOrInterfaceDeclaration();
                if(iServiceDoc != null && cd.getExtendedTypes().size() > 0) {
                    for (ClassOrInterfaceType cit : cd.getExtendedTypes()) {
                        if(I_SERVICE.equals(cit.getName().getIdentifier()) &&
                                cit.getTypeArguments().isPresent() &&
                                cit.getTypeArguments().get().size() == 2) {

                            Type entityType = cit.getTypeArguments().get().get(0);
                            Type idType = cit.getTypeArguments().get().get(1);

                            // 把泛型替换为实际类型
                            for(MethodDeclaration md : iServiceDoc.getMethodDeclarations()) {
                                md = md.clone();
                                if("T".equals(md.getType().toString())) {
                                    md.setType(entityType);
                                } else if("K".equals(md.getType().toString())) {
                                    md.setType(idType);
                                }
                                for (Parameter parameter : md.getParameters()) {
                                    if("T".equals(parameter.getType().toString())) {
                                        parameter.setType(entityType);
                                    } else if("K".equals(parameter.getType().toString())) {
                                        parameter.setType(idType);
                                    }
                                }
                                visitor.getMethodDeclarations().add(md);
                            }
                            break;
                        }
                    }
                }

                if(visitor.getMethodDeclarations().size() > 0) {
                    insertServiceName(document, visitor.getClassOrInterfaceDeclaration());
                    for(MethodDeclaration n : visitor.getMethodDeclarations()) {
                        insertMethodBrief(document, n);
                        insertMethodDetail(document, n);
                        insertMethodParamTable(document, n);
                        insertReturnValue(document, n);
                    }
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }


        writeDoc(document, mavenProject.getBasedir()+"/接口文档.docx");
    }
}
